#pragma once
#include <new>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include <cstdint>

#include "utility.hpp"
#include "config/config_begin.h"

namespace cpp{
	namespace utility
	{
		namespace __memory
		{
			template<bool b>
			struct _Construct
			{
				template<class _Tp, class... Args>
				static void construct(_Tp* p, Args&& ... args)
				{
					::new (static_cast<void*>(p)) _Tp(std::forward<Args>(args)...);
				}
			};

			template<>
			struct _Construct<false >
			{
				template<class _Tp, class... Args>
				static void construct(_Tp* p, Args&& ... args)
				{
					::new (static_cast<void*>(p)) _Tp{ std::forward<Args>(args)... };
				}
			};
		}

		template<class _Tp, class... Args>
		inline void construct_perfect(_Tp* _p, Args&& ... args)
		{
			__memory::_Construct<std::is_constructible<_Tp, Args...>::value >::construct(_p, std::forward<Args>(args)...);
		}
	}


	namespace utility
	{
		template<class _Alloc,
			class _Ty,
			class... _Types,
			class _Dont_care>
			void construct(_Alloc&&, std::false_type, _Dont_care,
						   _Ty *_Ptr, _Types&&... _Args)
		{
			::new (static_cast<void *>(_Ptr)) _Ty(std::forward<_Types>(_Args)...);
		}

		template<class _Alloc,
			class _Ty,
			class... _Types>
			void construct(_Alloc&& alloc, std::true_type, std::false_type,
						   _Ty *_Ptr, _Types&&... _Args)
		{
			::new (static_cast<void *>(_Ptr)) _Ty(std::allocator_arg, std::forward<_Alloc>(alloc),
												  std::forward<_Types>(_Args)...);
		}


		template<class _Alloc,
			class _Ty,
			class... _Types>
			void construct(_Alloc&& alloc, std::true_type, std::true_type,
						   _Ty *_Ptr, _Types&&... _Args)
		{
			::new (static_cast<void *>(_Ptr)) _Ty(std::forward<_Types>(_Args)..., std::forward<_Alloc>(alloc));
		}


		template <class _Alloc, typename T, typename... Args>
		void construct_forward_allocator(_Alloc&& alloc, T *_Ptr, Args&&... _Args)
		{
			construct(std::forward<_Alloc>(alloc),
					  std::uses_allocator<T, _Alloc >(),
					  std::is_constructible<T, Args..., _Alloc >(),
					  _Ptr,
					  std::forward<Args>(_Args)...);
		}

		//////////////////////////////////////////////////////////////////////////


		template<class _Alloc,
			class... _Atypes,
			class _Dont_care>
			std::tuple<_Atypes...>
			_construct_tuple(_Alloc&&, std::false_type, _Dont_care,
							 std::tuple<_Atypes...>& _Val)
		{
			return std::move(_Val);
		}

		template<class _Alloc,
			class... _Atypes>
			std::tuple<std::allocator_arg_t, _Alloc&, _Atypes...>
			_construct_tuple(_Alloc&& alloc, std::true_type, std::false_type,
							 std::tuple<_Atypes...>& _Val)
		{
			return (std::tuple_cat(std::tuple<std::allocator_arg_t, _Alloc&>(std::allocator_arg, alloc), std::move(_Val)));
		}

		template<class _Alloc,
			class... _Atypes>
			std::tuple<_Atypes..., _Alloc&>
			_construct_tuple(_Alloc&& alloc, std::true_type, std::true_type,
							 std::tuple<_Atypes...>& _Val)
		{
			return (std::tuple_cat(std::move(_Val), std::tuple<_Alloc&>(alloc)));
		}



		//////////////////////////////////////////////////////////////////////////
		template<class _Alloc,
			class _Ty1,
			class _Ty2,
			class... Args1,
			class... _Uy2>
			void construct_pair(_Alloc&& alloc, std::pair<_Ty1, _Ty2> *_Ptr,
								std::tuple<Args1...>& _Val1, std::tuple<_Uy2...>& _Val2)
		{	
			::new (static_cast<void *>(_Ptr))
				std::pair<_Ty1, _Ty2>(std::piecewise_construct,
									  _construct_tuple(
										  alloc,
										  std::uses_allocator<_Ty1, _Alloc>(),
										  std::is_constructible<_Ty1, Args1..., _Alloc>(),
										  _Val1),
									  _construct_tuple(
										  alloc,
										  std::uses_allocator<_Ty2, _Alloc>(),
										  std::is_constructible<_Ty2, _Uy2..., _Alloc>(),
										  _Val2));
		}


		template<class _Alloc,
			class T1, class T2,
			class... Args1,
			class... Args2>
			void construct_forward_allocator(_Alloc&& alloc,
											 std::pair<T1, T2>* _Ptr,
											 std::piecewise_construct_t,
											 std::tuple<Args1...> x,
											 std::tuple<Args2...> y)
		{
			construct_pair(std::forward<_Alloc>(alloc), _Ptr, x, y);
		}




		template <class _Alloc, class T1, class T2>
		void construct_forward_allocator(_Alloc&& alloc, std::pair<T1, T2>* p)
		{
			construct_forward_allocator(std::forward<_Alloc>(alloc), p, std::piecewise_construct,
										std::tuple<>(), std::tuple<>());
		}


		template <class _Alloc, class T1, class T2, class U, class V>
		void construct_forward_allocator(_Alloc&& alloc, std::pair<T1, T2>* p, U&& x, V&& y)
		{
			construct_forward_allocator(std::forward<_Alloc>(alloc), p, std::piecewise_construct,
										std::forward_as_tuple(std::forward<U>(x)), std::forward_as_tuple(std::forward<V>(y)));
		}


		template <class _Alloc, class T1, class T2, class U, class V>
		void construct_forward_allocator(_Alloc&& alloc, std::pair<T1, T2>* p,
										 const std::pair<U, V>& pr)
		{
			construct_forward_allocator(std::forward<_Alloc>(alloc), p, std::piecewise_construct,
										std::forward_as_tuple(pr.first), std::forward_as_tuple(pr.second));

		}

		template <class _Alloc, class T1, class T2, class U, class V>
		void construct_forward_allocator(_Alloc&& alloc, std::pair<T1, T2>* p, std::pair<U, V>&& pr)
		{
			construct_forward_allocator(std::forward<_Alloc>(alloc), p, std::piecewise_construct,
										std::forward_as_tuple(std::forward<U>(pr.first)),
										std::forward_as_tuple(std::forward<V>(pr.second)));
		}

	}
}



namespace cpp
{

	template< class W >
	class observer_ptr
	{
		static_assert(!std::is_reference<W>::value, "W shall not be a reference type, but may be an incomplete type");
	public:

		using	element_type = W;
		using	pointer = std::add_pointer_t<W>;

		constexpr observer_ptr() noexcept :_pointer(nullptr)	{}

		constexpr observer_ptr(nullptr_t) noexcept : _pointer(nullptr){	}


		explicit observer_ptr(pointer p) noexcept : _pointer(p){}

		template<class W2, class = std::enable_if_t<std::is_convertible<typename observer_ptr<W2>::pointer, pointer >::value > >
		observer_ptr(observer_ptr<W2> other) noexcept : _pointer(other._pointer)	{}

		observer_ptr(const observer_ptr& other) noexcept = default;

		observer_ptr(observer_ptr&& other) noexcept	:_pointer(other._pointer){	}

		constexpr void reset(pointer p = nullptr) noexcept
		{
			_pointer = p;
		}

		constexpr pointer release()	noexcept
		{
			auto tmp = _pointer;
			_pointer = nullptr;
			return tmp;
		}


		constexpr void swap(observer_ptr& other) noexcept
		{
			std::swap(_pointer, other._pointer);
		}

		constexpr pointer get() const noexcept
		{
			return _pointer;
		}

		constexpr explicit operator bool() const noexcept
		{
			return _pointer != nullptr;
		}


		constexpr std::add_lvalue_reference_t<W> operator*() const noexcept
		{
			return *_pointer;
		}

		constexpr pointer operator->() const noexcept
		{
			return _pointer;
		}

		constexpr explicit operator std::add_pointer_t<W>() const noexcept
		{
			return _pointer;
		}
	private:

		pointer _pointer;
	};

	template <class W>
	observer_ptr<W> make_observer(W* p) noexcept
	{
		return observer_ptr<W>(p);
	}


	template<class W1, class W2>
	bool operator==(const observer_ptr<W1>& p1, const observer_ptr<W2>& p2) noexcept
	{
		return p1.get() == p2.get();
	}

	template<class W1, class W2>
	bool operator!=(const observer_ptr<W1>& p1, const observer_ptr<W2>& p2) noexcept
	{
		return p1.get() != p2.get();
	}


	template<class W>
	bool operator==(const observer_ptr<W>& p, std::nullptr_t)noexcept
	{
		return !p;
	}

	template<class W>
	bool operator==(std::nullptr_t, const observer_ptr<W>& p) noexcept
	{
		return !p;
	}


	template<class W>
	bool operator!=(const observer_ptr<W>& p, std::nullptr_t)noexcept
	{
		return static_cast<bool>(p);
	}


	template<class W>
	bool operator!=(std::nullptr_t, const observer_ptr<W>& p) noexcept
	{
		return static_cast<bool>(p);

	}


	template<class W1, class W2>
	bool operator<(const observer_ptr<W1>& p1, const observer_ptr<W2>& p2) noexcept
	{
		return p1.get() < p2.get();
	}


	template<class W1, class W2>
	bool operator>(const observer_ptr<W1>& p1, const observer_ptr<W2>& p2)noexcept
	{
		return  p2 < p1;
	}

	template<class W1, class W2>
	bool operator<=(const observer_ptr<W1>& p1, const observer_ptr<W2>& p2)noexcept
	{
		return !(p2 < p1);
	}

	template<class W1, class W2>
	bool operator>=(const observer_ptr<W1>& p1, const observer_ptr<W2>& p2) noexcept
	{
		return !(p1 < p2);
	}

	template<class W>
	void swap(observer_ptr<W>& l, observer_ptr<W>& r) noexcept
	{
		l.swap(r);
	}

}




namespace cpp
{
	namespace utility
	{
		namespace __memory
		{
			template<size_t V>
			struct log2
			{
				enum { value = log2<V / 2>::value + 1 };
			};

			template<>
			struct log2<1>
			{
				enum { value = 0 };
			};
		}

		template<class T>
		class tagged_ptr
		{
		public:
			using	element_type = T;
			using	pointer = std::add_pointer_t<T>;

			tagged_ptr() noexcept : _ptr(nullptr){}

			tagged_ptr(pointer ptr, std::uint8_t tag = 0)noexcept : _ptr(ptr){ this->tag(tag); }

			pointer operator->() const noexcept
			{
				return get();
			}

			std::add_lvalue_reference_t<T> operator*() const
			{
				return *get();
			}

			pointer get() const noexcept
			{
				return  reinterpret_cast<T*>((std::uintptr_t)_ptr & ~tag_mask);
			}

			std::uint8_t tag() const noexcept
			{
				return static_cast<std::uint8_t>((std::uintptr_t)_ptr & tag_mask);
			}

			std::uint8_t tag(std::uint8_t tag) noexcept
			{
				_ptr = reinterpret_cast<pointer>((std::uintptr_t)(get()) | (tag & tag_mask));
				return this->tag();
			}


			constexpr explicit operator bool() const noexcept
			{
				return get() != nullptr;
			}

			constexpr explicit operator std::add_pointer_t<T>() const noexcept
			{
				return get();
			}

			constexpr void reset(pointer p = nullptr) noexcept
			{
				_ptr = reinterpret_cast<pointer>(((std::uintptr_t)p & ~tag_mask) | ((std::uintptr_t)_ptr & tag_mask));
			}

			constexpr pointer release()	noexcept
			{
				auto tmp = get();
				reset();
				return tmp;
			}

			constexpr void swap(tagged_ptr& other) noexcept
			{
				auto t = tag();
				auto t2 = other.tag();
				std::swap(_ptr, other._ptr);
				tag(t);
				other.tag(t2);
			}


			static const std::size_t tag_bits = __memory::log2<alignof(T)>::value;

		private:
			static const std::uintptr_t tag_mask = static_cast<std::uintptr_t>(alignof(T)-1);
			static const std::size_t max_tag = (1 << tag_bits) - 1;
			static_assert(max_tag > 0, "can't store tag, should use raw pointer");

			pointer _ptr;
		};

		template<class T>
		void swap(tagged_ptr<T> & l, tagged_ptr<T> & r) noexcept
		{
			l.swap(r);
		}
	}
}

#include "config/config_end.h"
